/**
 author: chengzheng
 date: 2017/12/26
 email: 13120207061@163.com
 */
const redis = require('./redis');
const http = require('./downloader/http');
const browser = require('./downloader/headless/browser');
const phantom = require('./downloader/headless/phantomjs');
var logger = require("./logger");

class Consumer {
    constructor(option) {
        this.channel = option.channel;
        this.sleepTime = option.sleepTime || 3000;
        this.userAgent = "";
        if (!!option.userAgent && typeof option.userAgent == 'string') {
            this.userAgent = option.userAgent;
        }
        this.deviceType = option.deviceType;
        this.redisConf = option.dbConf.redis;
    }


    /**
     * start to crawl
     * @returns {Promise.<void>}
     */
    async go() {
        logger.info(`spiderman consumer instance go,connnect to redis queue ${this.redisConf.host}`);
        try {
            redis.connect({
                host: this.redisConf.host,
                port: this.redisConf.port,
                password: this.redisConf.password

            })
        } catch (err) {
            logger.error(`redis connect failed,err message:${err.toString()}`);
        }
        await  this.waitUntilTask();
    }

    async waitUntilTask() {
        let firstTask = await this.getTaskFromQueue();

        if (firstTask == null) {
            //retry search
            let hasSeleepInfoLogShown = false;
            this.intervalInst = setInterval(
                async function () {
                    if (!hasSeleepInfoLogShown) {
                        logger.info(`attemp to get task from queue of channel <${this.channel}>`);
                        hasSeleepInfoLogShown = true;
                    } else {
                        logger.debug(`attemp to get task from queue of channel <${this.channel}>`);
                    }

                    let task = await this.getTaskFromQueue();
                    if (task !== null) {
                        clearInterval(this.intervalInst);
                        let result = await this.startDownloadTask(task);
                        if (typeof result == 'object' && !!result.error) {
                            this.whenTaskFailed(task, result.error);
                            setTimeout(this.waitUntilTask.bind(this), this.sleepTime);
                        } else {
                            await this.setTaskFinish(task.id);
                            await this.waitUntilTask();
                        }
                    } else {
                        if (!hasSeleepInfoLogShown) {
                            logger.info(`task queue is empty,sleetp until get task`);
                            hasSeleepInfoLogShown = true;
                        } else {
                            logger.debug(`task queue is empty,sleep unstail get task`);
                        }
                    }


                }.bind(this), this.sleepTime);

        }
        else {
            let result = await this.startDownloadTask(firstTask);
            if (typeof result == 'object' && !!result.error) {
                this.whenTaskFailed(firstTask, result.msg);
                setTimeout(this.waitUntilTask.bind(this), this.sleepTime);
            } else {
                await this.setTaskFinish(firstTask.id);
                //循环下一个任务
                await this.waitUntilTask();
            }
        }
    }

    async getTaskFromQueue() {
        let task = await  redis.lpop({
            tableName: "queue:" + this.channel
        });

        if (task == null) {
            return null;
        }

        logger.debug(`task pop from queue ${this.channel},task id:${task.id}`);
        await redis.hset({
            tableName: 'tmphas:' + this.channel,
            key: task.id,
            value: task
        });

        logger.debug(`task is executing:${task.id}`);
        return task;

    }

    async startDownloadTask(task) {
        let crawlResult = {};
        try {
            if (task.spiderType == 'plain') {
                crawlResult = await this.downloadAndParser(task);
            } else if (task.spiderType == 'browser') {
                crawlResult = await this.browserRequest(task.url);
            } else if (task.split == 'phantom') {
                crawlResult = await this.phantomRequest(task);
            }

        } catch (err) {
            crawlResult = {
                crawlError: err.toString()
            };
            logger.warn(`some unknown error during crawling:${err.toString()}`);
        }

        if (!!crawlResult.crawlError) {
            return {
                err: true,
                msg: 'unknown error'
            };
        }
        else if (crawlResult == 'crawl failed') {
            return {
                err: true,
                msg: 'crawl failed'
            }
        }
        else if (crawlResult == 'invalide html') {
            return {
                err: true,
                msg: 'invalide html'
            }
        }
        else {
            await this.afterCrawlRequest(crawlResult);
            return true;
        }
    }

    whenTaskFailed(firstTask, msg) {
        logger.error(`task failed,id:${firstTask.id}`);
    }

    async setTaskFinish(taskId) {
        await redis.hdel({
            tableName: 'tmphash:' + this.channel,
            key: taskId
        });
        logger.info(`task finish,id:${taskId}`);
        return true;
    }

    /**
     * 发送http请求 并且解析
     * @param url
     */
    async downloadAndParser(task) {
        logger.debug(`http request,url:${task.url}`);
        let html;

        html = await http({
            url: task.url,
            proxy: task.proxy,
            headers: task.headers
        })

        if (html == 'crawl failed') {
            logger.error(`http request failed,url:${task.url}`);
            return 'crawl failed';
        }
        let isValidHtml = await this.beforeParseHtml(html);
        if (!isValidHtml) {
            logger.error(`http request got invalid result,url:${task.url}`);
            return 'invalid html';
        }
        logger.debug(`http request has finished,url:${task.url}`);
        let output = this.parseHtml(html);
        return output;
    }

    async beforeParseHtml(html) {
        //hook

    }

    parseHtml(html) {
        //hook
        return html;
    }

    async browserRequest(url) {
        let viewPortSet = {};
        if (this.deviceType === 'mobile') {
            viewPortSet = {
                width: 320,
                height: 568,
                isMobile: true
            }
        }
        else if (this.deviceType == 'pc') {
            viewPortSet = {
                width: 1440,
                height: 960
            }
        }

        else {
            viewPortSet = {
                width: 1000,
                height: 1000
            }
        }

        logger.debug(`headless browser request,url:${url}`);
        let html = await browser.request({
            url: url,
            userAgent: this.userAgent,
            viewPort: viewPortSet
        })

        if (html == 'crawl failed') {
            logger.error(`headless browser request failed,url:${url}`);
            return 'crawl failed';
        }

        let isValidHtml = await this.beforeParseHtml(html);
        if (!isValidHtml) {
            logger.error(`headless browser request got invalid result,url=${url}`);
            return 'invalid html';
        }

        logger.debug(`headless browser request has finished,url:${url}`);
        let output = this.parseHtml(html);
        return output;
    }


    async afterCrawlRequest(result) {
        logger.info(`crawl result: ${result}`);
    }

    async phantomRequest(task) {
        let html = await phantom.request({
            url: task.url,
            userAgent: this.userAgent,
        })

        if (html == 'crawl failed') {
            logger.error(`headless phantom request failed,url:${url}`);
            return 'crawl failed';
        }

        let isValidHtml = await this.beforeParseHtml(html);
        if (!isValidHtml) {
            logger.error(`headless phantom request got invalid result,url=${url}`);
            return 'invalid html';
        }

        logger.debug(`headless phantom request has finished,url:${url}`);
        let output = this.parseHtml(html);
        return output;
    }
}

module.exports = Consumer;
